package com.nowcoder.community.service;

import com.nowcoder.community.dao.LoginTicketMapper;
import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.LoginTicket;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.MailClient;
import com.nowcoder.community.util.RedisKeyUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName UserService
 * @Description
 * @Author hhwu
 * @Date 2022/5/16 22:31
 * Version 1.0
 **/
@Service
public class UserService implements CommunityConstant {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MailClient mailClient;

    // 在controller层，可以直接得到modelandview
    // 在service层，发送邮件时，将model和view组合，形成html页面。
    @Autowired
    private TemplateEngine templateEngine;

    @Value("${community.path.domain}")// 注入常量时用Value注解
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Autowired
    private RedisTemplate redisTemplate;

//    @Autowired
//    private LoginTicketMapper loginTicketMapper;

    public User findUserById(int id) {
        /**
         * @Author hhwu
         * @Description //TODO 通过用户id查询用户的所有信息
         * @Date 20:37 2022/5/18
         * @Param [id]
         * @return com.nowcoder.community.entity.User
         **/
//        return userMapper.selectById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }

    public Map<String,Object> register(User user) {
        /**
         * @Author hhwu
         * @Description //TODO 如果用户输入的注册信息不合法，则返回提示信息；如果合法，则将用户注册信息输入数据库，并发送激活邮件
         * @Date 20:37 2022/5/18
         * @Param [user]
         * @return java.util.Map<java.lang.String,java.lang.Object>
         **/
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        // 前端会自动进行空值处理的，保证填写的内容不为空，因此这里不写也行
        if (user == null) {
            throw new IllegalArgumentException("参数不能为空！");
        }
        if (StringUtils.isBlank(user.getUsername())) {
            map.put("usernameMsg", "账号不能为空！");
            return map;
        }
        if (StringUtils.isBlank(user.getPassword())) {
            map.put("passwordMsg", "密码不能为空！");
            return map;
        }
        if (StringUtils.isBlank(user.getEmail())) {
            map.put("emailMsg", "邮箱不能为空！");
            return map;
        }

        // 验证账号是否存在
        User u = userMapper.selectByName(user.getUsername());
        if (u != null) {
            map.put("usernameMsg", "该账号已存在！");
            return map;
        }
        // 验证邮箱是否存在
        u = userMapper.selectByEmail(user.getEmail());
        if (u != null) {
            map.put("emailMsg", "该邮箱已被注册！");
            return map;
        }

        // 注册用户
        user.setSalt(CommunityUtil.generateUUID().substring(0, 5));// 额外加5位扩展密码
        user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));// 密码加密
        user.setType(0);// 设置普通用户
        user.setStatus(0);// 未激活状态
        user.setActivationCode(CommunityUtil.generateUUID()); // 设置激活码
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));// 生成随机图像
        user.setCreateTime(new Date());// 生成注册日期
        userMapper.insertUser(user);// 用户信息写入数据库

        // 激活邮件
        // 这份邮件中有一个链接，链接上携带（GET请求）了用户ID和激活码，如果用户点击该链接，则直接可以对链接地址传入ID和激活码；而如果用户手工修改了ID或激活码，则传入链接地址的ID和激活码就会与数据库中的不匹配
        Context context = new Context(); // 相当于controller层中的model数据封装
        context.setVariable("email", user.getEmail());
        // 激活路径格式：http://localhost:8080/community/activation/101/code
        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();// 设置激活链接
        context.setVariable("url", url);
        String content = templateEngine.process("/mail/activation", context);// 封装邮件内容：modelandview
        mailClient.sendMail(user.getEmail(), "激活邮件", content);// 发送邮件

        return map;
    }

    public int activation(int userId, String code) {
        /**
         * @Author hhwu
         * @Description //TODO 将传来的用户激活码数据和数据库中的进行比对，返回激活结果
         * @Date 20:35 2022/5/18
         * @Param [userId, code]
         * @return int
         **/
        User user = userMapper.selectById(userId);// 查询当前账号状态
        if (user.getStatus() == 1) {
            return ACTIVATION_REPEAT;
        } else if (user.getActivationCode().equals(code)) {
            userMapper.updateStatus(userId, 1);// 未激活时并且激活码正确，将激活状态写入数据库
            // 因为MySQL数据库中已经对user对象的状态加以修改，这就造成了redis缓存中的user数据和sql中的不一致，就清除掉redis中的。以后用的user对象时在重新将sql中的user对象赋值给redis中
            clearCache(userId);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        /**
         * @Author hhwu
         * @Description //TODO 如果认证通过，则返回ticket，否则返回不合法信息
         * @Date 15:32 2022/5/19
         * @Param [username, password, expiredSeconds]
         * @return java.util.Map<java.lang.String,java.lang.Object>
         **/
        
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空");
            return map;
        }

        // 验证账号
        User user = userMapper.selectByName(username);
        if (user == null) {
            map.put("usernameMsg", "账号不存在！");
            return map;
        }
        password = CommunityUtil.md5(password + user.getSalt());// 将用户输入的密码进行加密后比较
        if (!user.getPassword().equals(password)) {// 这里要使用equals判断相等，而不能用!=
            map.put("passwordMsg", "密码错误！");
            return map;
        }
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "账号未激活！");
            return map;
        }

        // 认证通过，生成凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));// 单位为秒
        loginTicket.setStatus(0);
//        loginTicketMapper.insertLoginTicket(loginTicket);
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);// redis会自动把loginTicket对象序列化为字符串

        // 返回ticket信息
        map.put("ticket", loginTicket.getTicket());
        return map;
    }

    public void logout(String ticket) {
        /**
         * @Author hhwu
         * @Description //TODO 退出，将凭证状态改为1。而不是删除凭证，因为凭证的历史记录对于将来的功能可能有用
         * @Date 10:39 2022/5/20
         * @Param [ticket]
         * @return void
         **/
//        loginTicketMapper.updateStatus(ticket, 1);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        // 修改ticket状态
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey, loginTicket);
    }

    public LoginTicket findLoginTicket(String ticket) {
        /**
         * @Author hhwu
         * @Description //TODO 根据ticket值在数据库中查询该条记录并返回
         * @Date 13:07 2022/5/20
         * @Param [ticket]
         * @return com.nowcoder.community.entity.LoginTicket
         **/
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

    public int updateHeader(int userId, String headerUrl) {
        /**
         * @Author hhwu
         * @Description //TODO 更新用户头像地址
         * @Date 13:07 2022/5/21
         * @Param [ticket]
         * @return com.nowcoder.community.entity.LoginTicket
         **/
//        return userMapper.updateHeader(userId, headerUrl);
        int rows = userMapper.updateHeader(userId, headerUrl);
        // 因为MySQL数据库中已经对user对象的状态加以修改，这就造成了redis缓存中的user数据和sql中的不一致，就清除掉redis中的。以后用的user对象时在重新将sql中的user对象赋值给redis中
        clearCache(userId);
        return rows;
    }

    public int updatePassword(int userId, String password) {
        /**
         * @Author hhwu
         * @Description //TODO 更新用户密码，而salt不变
         * @Date 13:07 2022/5/21
         * @Param [ticket]
         * @return com.nowcoder.community.entity.LoginTicket
         **/
        return userMapper.updatePassword(userId, password);
    }

    public User findUserByName(String username) {
        /**
         * @Author hhwu
         * @Description //TODO 通过用户名查找用户
         * @Date 10:05 2022/5/25
         * @Param [username]
         * @return com.nowcoder.community.entity.User
         **/
        return userMapper.selectByName(username);
    }

    // 1.优先从缓存中取值
    private User getCache(int userId) {
        /**
         * @Author hhwu
         * @Description //TODO 从redis缓存中获取指定用户对象
         * @Date 17:56 2022/5/26
         * @Param [userId]
         * @return com.nowcoder.community.entity.User
         **/
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        /**
         * @Author hhwu
         * @Description //TODO 向redis缓存中加入指定用户对象
         * @Date 17:57 2022/5/26
         * @Param [userId]
         * @return com.nowcoder.community.entity.User
         **/
        User user = userMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
        return user;
    }

    // 3.数据变更时清除缓存数据
    private void clearCache(int userId) {
        /**
         * @Author hhwu
         * @Description //TODO 删除redis中的指定用户对象。但对sql中的user对象有修改时，就要调用该方法，因为此时redis和sql中的user数据已经不一致了。下次用到user时，再从sql中奖其调到redis中
         * @Date 17:57 2022/5/26
         * @Param [userId]
         * @return void
         **/
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

    public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
        /**
         * @Author hhwu
         * @Description //TODO 查询用户对应的权限
         * @Date 11:02 2022/5/31
         * @Param [userId]
         * @return java.util.Collection<? extends org.springframework.security.core.GrantedAuthority>
         **/
        User user = this.findUserById(userId);

        List<GrantedAuthority> list = new ArrayList<>();
        list.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                switch (user.getType()) {
                    case 1:
                        return AUTHORITY_ADMIN;
                    case 2:
                        return AUTHORITY_MODERATOR;
                    default:
                        return AUTHORITY_USER;
                }
            }
        });
        return list;
    }
}
